// Copyright 2001-2005 freehep
package org.freehep.graphicsio.font;

import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphMetrics;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

import org.freehep.graphics2d.font.CharTable;
import org.freehep.util.io.ASCIIHexOutputStream;
import org.freehep.util.io.CountedByteOutputStream;
import org.freehep.util.io.EEXECEncryption;

/**
 * Font embedder for type 1 fonts. The output can be directly fed into a ps file
 * or as a FontFile to a pdf file.
 * <h3>Todo</h3>
 * <ul>
 * <li>use subroutines for accents
 * <li>add more hints
 * </ul>
 * 
 * @author Simon Fischer
 * @version $Id: FontEmbedderType1.java,v 1.4 2009-08-17 21:44:45 murkle Exp $
 */
public class FontEmbedderType1 extends FontEmbedder {

	/** Defines whether or not eexec encryption is used. */
	private static final boolean ENCRYPT = true;

	/**
	 * Defines whether or not encrypted part should be hex encoded (otherwise it
	 * is binary).
	 */
	private static final boolean HEX_ENC = true;

	/**
	 * Defines whether or not encrypted charstrings should be hex encoded
	 * Ghostview crashes when set to true. The freehep ps interpreter handles it
	 * correctly.
	 */
	private static final boolean HEX_ENC_CHARSTRINGS = false;

	private PrintStream fontFile, encrypted;

	private CountedByteOutputStream byteCounter;

	private int asciiEnd, encEnd; // remember the lengths of the three
									// portions

	private boolean addZeros;

	public FontEmbedderType1(FontRenderContext context, OutputStream out,
			boolean addZeros) {
		super(context);
		this.byteCounter = new CountedByteOutputStream(out);
		this.fontFile = new PrintStream(byteCounter);
		this.addZeros = addZeros;
		asciiEnd = encEnd = -1;
	}

	@Override
	protected void writeWidths(double[] w) throws IOException {
	}

	@Override
	protected void writeEncoding(CharTable t) throws IOException {
		fontFile.println("/Encoding 256 array");
		fontFile.println("0 1 255 {1 index exch /.notdef put} for"); // set
																		// undefined
																		// to
																		// .notdef
																		// ??
		for (int i = 0; i < 256; i++) {
			String charName = t.toName(i);
			if (charName != null) {
				fontFile.println("dup " + i + " /" + charName + " put");
			}
		}
		fontFile.println("readonly def");
	}

	@Override
	protected void openIncludeFont() throws IOException {

		// begin clear text ascii portion
		fontFile.println("%!FontType1-1.0: " + getFont().getName()); // unknown
																		// version
																		// number
		// fontFile.println("%%CreationDate: " +
		// DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).
		// format(new Date()));
		fontFile.println("% Generated by: " + getClass().getName());
		fontFile.println("11 dict begin");

		fontFile.println("/FontInfo 8 dict dup begin");
		fontFile.println(
				"/FullName (" + getFont().getPSName() + ") readonly def");
		fontFile.println(
				"/FamilyName (" + getFont().getFamily() + ") readonly def");
		fontFile.println("end readonly def");

		fontFile.println("/FontName /" + getFontName() + " def");
		fontFile.println("/PaintType 0 def");
		fontFile.println("/FontType 1 def");
		fontFile.println("/FontMatrix [" + 1 / FONT_SIZE + " 0.0 0.0 "
				+ 1 / FONT_SIZE + " 0.0 0.0] readonly def");
	}

	@Override
	protected void closeIncludeFont() {

		Rectangle2D boundingBox = getFontBBox();
		int llx = (int) Math.round(boundingBox.getX());
		int lly = (int) Math.round(boundingBox.getY());
		int urx = (int) Math.round(boundingBox.getX() + boundingBox.getWidth());
		int ury = (int) Math
				.round(boundingBox.getY() + boundingBox.getHeight());
		fontFile.println("/FontBBox {" + llx + " " + lly + " " + urx + " " + ury
				+ "} readonly def");

		fontFile.println("currentdict end");

		// begin encrypted portion
		if (ENCRYPT) {
			fontFile.print("currentfile eexec ");
			asciiEnd = byteCounter.getCount();
		}
		fontFile.flush();
	}

	@Override
	protected void openGlyphs() throws IOException {
		// begin encryption
		if (ENCRYPT) {
			if (HEX_ENC) {
				encrypted = new PrintStream(
						new EEXECEncryption(new ASCIIHexOutputStream(fontFile),
								EEXECEncryption.EEXEC_R));
			} else {
				encrypted = new PrintStream(
						new EEXECEncryption(fontFile, EEXECEncryption.EEXEC_R));
			}
		} else {
			encrypted = fontFile;
		}

		// begin the Private dictionary
		encrypted.println("dup /Private 8 dict dup begin");
		encrypted.println(
				"/RD {string currentfile exch readstring pop} executeonly def");
		encrypted.println("/ND {noaccess def} executeonly def");
		encrypted.println("/NP {noaccess put} executeonly def");
		encrypted.println("/BlueValues [] def"); // ???
		encrypted.println("/MinFeature {16 16} def");
		encrypted.println("/password 5839 def");
		encrypted.print("2 index ");
		encrypted.println("/CharStrings " + (getNODefinedChars() + 1)
				+ " dict dup begin");
	}

	@Override
	protected void closeGlyphs() throws IOException {
		encrypted.println("end"); // end Private
		encrypted.println("end"); // end CharStrings
	}

	@Override
	protected void closeEmbedFont() throws IOException {
		encrypted.println("readonly put");
		encrypted.println("noaccess put");
		encrypted.println("dup /FontName get exch definefont pop");
		encrypted.print("mark");
		if (ENCRYPT) {
			encrypted.print(" currentfile closefile ");
		}
		encrypted.flush();
		encEnd = byteCounter.getCount();
		if (!ENCRYPT) {
			asciiEnd = encEnd;
		}

		if (addZeros) {
			fontFile.println();
			for (int i = 0; i < 16; i++) {
				fontFile.println("00000000000000000000000000000000");
			}
			fontFile.println("cleartomark");
		}
	}

	@Override
	protected void writeGlyph(String characterName, Shape glyph,
			GlyphMetrics glyphMetrics) throws IOException {

		// FIXME: find out why Acrobat Reader displays some characters displaced
		// when
		// using the correct sidebearing. A value of 0 looks good
		double sidebearing = glyphMetrics != null ? glyphMetrics.getLSB() : 0;
		// double sidebearing = 0;

		// write the binary charstring to a buffer
		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		CharstringEncoder charString = (HEX_ENC_CHARSTRINGS
				? new CharstringEncoder(
						new EEXECEncryption(new ASCIIHexOutputStream(bytes),
								EEXECEncryption.CHARSTRING_R))
				: new CharstringEncoder(new EEXECEncryption(bytes,
						EEXECEncryption.CHARSTRING_R)));

		charString.startChar(sidebearing, (glyphMetrics != null
				? glyphMetrics.getAdvance() : getUndefinedWidth())); // bounds.getWidth());
		charString.drawPath(glyph);
		charString.endchar();

		// write the buffer to the encrypted fontFile
		byte[] binaryString = bytes.toByteArray();
		encrypted.print(
				"/" + characterName + " " + binaryString.length + " RD ");
		for (int i = 0; i < binaryString.length; i++) {
			encrypted.write(binaryString[i] & 0x00ff);
		}
		encrypted.println("ND");
		encrypted.flush();
	}

	/** Returns the length of the ascii portion of the output. */
	public int getAsciiLength() {
		return asciiEnd;
	}

	/** Returns the length of the encrypted portion of the output. */
	public int getEncryptedLength() {
		return encEnd - asciiEnd;
	}

}
